/*
 * GRoundRobinContestElement.java
 * Created on 27 juin 2007
 *
 * Glacéo Internet Platform
 * http://sourceforge.net/projects/chm/
 *
 * Copyright (c) 2005-2007, Jan Janke (VirtualHockey Project)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hockeo.vhbase.data.dao.contest;


import java.util.List;

import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

import com.hockeo.vhbase.conf.GRulesConstants;
import com.hockeo.vhbase.data.dao.IGEntity;
import com.hockeo.vhbase.data.dao.season.GMatchday;
import com.hockeo.vhbase.data.dao.season.GMoveday;
import com.hockeo.vhbase.data.dao.user.GComputerUser;

/**
 * Represents a single round robin pool which may be included in any contest.
 *
 * @version $Id$
 * @author jjanke
 */
@Entity
@DiscriminatorValue( "RR" )
public class GRoundRobinContestElement extends GContestElement
{
  @Column( name = "rr_num_teams" )
  private int d_nNumTeams;

  @Column( name = "rr_num_rounds" )
  private int d_nNumRounds;

  @Column( name = "rr_num_promoted_1" )
  private int d_nNumPromoted1;

  @Column( name = "rr_num_promoted_2" )
  private int d_nNumPromoted2;

  @Column( name = "rr_num_relegated" )
  private int d_nNumRelegated;

  /**
   * Creates a new round robin contest element.
   *
   * @param parent the parent contest round
   * @param strId the contest element ID
   * @param strLabel the contest element label
   * @param nNumTeams the number of participating teams
   * @param nNumRounds the number of rounds to be played
   * @param nNumPromoted1 the number of first level promoted teams
   * @param nNumPromoted2 the number of second level promoted teams
   * @param nNumRelegated the number of relegated teams
   */
  public GRoundRobinContestElement( GContestRound parent, String strId, String strLabel, int nNumTeams, int nNumRounds,
                                    int nNumPromoted1, int nNumPromoted2, int nNumRelegated )
  {
    super( parent, strId, strLabel );

    d_nNumTeams = nNumTeams;
    d_nNumRounds = nNumRounds;
    d_nNumPromoted1 = nNumPromoted1;
    d_nNumPromoted2 = nNumPromoted2;
    d_nNumRelegated = nNumRelegated;
  }

  /** Default parameterless constructor. */
  protected GRoundRobinContestElement()
  {
    super();
  }

  /**
   * Constructor to be used for testing ONLY.
   *
   * @param nNumTeams the number of teams participating in this contest element
   * @param nNumRounds the number of round robin rounds to be played
   */
  GRoundRobinContestElement( int nNumTeams, int nNumRounds )
  {
    d_nNumTeams = nNumTeams;
    d_nNumRounds = nNumRounds;
  }

  /*
   * @see com.hockeo.vhbase.data.GContestElement#getNumMatchdays()
   */
  @Override
  public int getNumMatchdays()
  {
    final int nNumTeams = getNumTeams();

    if ( nNumTeams % 2 == 0 )
      return ( nNumTeams - 1 ) * getNumRounds();
    else
      return nNumTeams * getNumRounds();
  }

  /*
   * @see com.hockeo.vhbase.data.GContestElement#getMinNumMovedays()
   */
  @Override
  public int getMinNumMovedays()
  {
    return (int) Math.ceil( getNumMatchdays() / (double) GRulesConstants.MAX_NUM_MATCHDAYS_PER_ROUND_ROBIN_MOVEDAY );
  }

  /**
   * Overridden in order to make sure that this contest element profits from additional
   * movedays if they are available.
   *
   * @return <code>true</code>
   * @see com.hockeo.vhbase.data.dao.contest.GContestElement#prefersAdditionalMovedays()
   */
  @Override
  public boolean prefersAdditionalMovedays()
  {
    return true;
  }

  /*
   * @see com.hockeo.vhbase.data.GContestElement#getNumMatchesPerClub()
   */
  @Override
  public int getNumMatchesPerClub()
  {
    return ( getNumTeams() - 1 ) * getNumRounds();
  }

  /*
   * @see com.hockeo.vhbase.data.GContestElement#attachMatchdaysToMovedays(java.util.List,
   * java.util.List,boolean)
   */
  @Override
  public void attachMatchdaysToMovedays( List<GMoveday> listMovedays, List<GMatchday> listMatchdays, boolean fUseMiddleMoveday )
  {
    if ( listMovedays == null || listMovedays.isEmpty() )
      throw new IllegalArgumentException( "Moveday list cannot be null or empty." );

    if ( listMatchdays == null || listMatchdays.isEmpty() )
      throw new IllegalArgumentException( "Matchday list cannot be null or empty." );

    int nNumMovedays = listMovedays.size();
    int nIdealNumMatchesPerMoveday;
    int nNumExtraMatchesPerMoveday;
    boolean fDontUseMiddleMoveday = false;
    boolean fExcludeLastMoveday = false;

    // If the number of available movedays is impair, the middle moveday is not used.
    //
    if ( !fUseMiddleMoveday && nNumMovedays % 2 != 0 )
    {
      nNumMovedays--;
      fDontUseMiddleMoveday = true;
    }

    nIdealNumMatchesPerMoveday = listMatchdays.size() / nNumMovedays;
    nNumExtraMatchesPerMoveday = listMatchdays.size() % nNumMovedays;

    // One array element per moveday indicating the number of attached matchdays
    //
    int[] anMatchdaysPerMoveday = new int[nNumMovedays];

    final int LAST_IDX_1ST_HALF = anMatchdaysPerMoveday.length / 2 - 1;
    final int LAST_IDX_2ND_HALF = anMatchdaysPerMoveday.length - 1;

    // If the number of teams is impair, there must be at least 3 matchdays at the end of
    // each half of the season
    //
    if ( nIdealNumMatchesPerMoveday < 3 && ( getNumTeams() % 2 != 0 ) )
    {
      int nNumFreelyDistributableMovedays = nNumMovedays - 2;
      int nNumMatchesForFreeDistribution = listMatchdays.size() - 6;

      nIdealNumMatchesPerMoveday = nNumMatchesForFreeDistribution / nNumFreelyDistributableMovedays;
      nNumExtraMatchesPerMoveday = nNumMatchesForFreeDistribution % nNumFreelyDistributableMovedays;

      anMatchdaysPerMoveday[LAST_IDX_1ST_HALF] = 3;
      anMatchdaysPerMoveday[LAST_IDX_2ND_HALF] = 3;

      fExcludeLastMoveday = true;
    }

    // Distribute minimum number of matchdays
    //
    for ( int i = 0; i < anMatchdaysPerMoveday.length; i++ )
    {
      if ( fExcludeLastMoveday && ( i == LAST_IDX_1ST_HALF || i == LAST_IDX_2ND_HALF ) )
        continue;

      anMatchdaysPerMoveday[i] = nIdealNumMatchesPerMoveday;
    }

    // Distribute extra matchdays
    //
    if ( nNumExtraMatchesPerMoveday > 0 )
    {
      // The remaining matchdays are distributed equally to the ends of both season halfs.
      // If the number of remaining matchdays is impair the second half receives one
      // matchday
      // more than the first half.
      //
      if ( nNumExtraMatchesPerMoveday % 2 == 0 )
      {
        int nExtraMatchesPerHalfSeason = nNumExtraMatchesPerMoveday / 2;

        for ( int i = LAST_IDX_1ST_HALF; i > LAST_IDX_1ST_HALF - nExtraMatchesPerHalfSeason; i-- )
        {
          if ( fExcludeLastMoveday && i == LAST_IDX_1ST_HALF )
            nExtraMatchesPerHalfSeason++;
          else
            anMatchdaysPerMoveday[i]++;
        }

        nExtraMatchesPerHalfSeason = nNumExtraMatchesPerMoveday / 2;

        for ( int i = LAST_IDX_2ND_HALF; i > LAST_IDX_2ND_HALF - nExtraMatchesPerHalfSeason; i-- )
        {
          if ( fExcludeLastMoveday && i == LAST_IDX_2ND_HALF )
            nExtraMatchesPerHalfSeason++;
          else
            anMatchdaysPerMoveday[i]++;
        }
      }
      else
      {
        int nExtraMatchesPerHalfSeason = nNumExtraMatchesPerMoveday / 2;

        for ( int i = LAST_IDX_1ST_HALF; i > LAST_IDX_1ST_HALF - nExtraMatchesPerHalfSeason; i-- )
        {
          if ( fExcludeLastMoveday && i == LAST_IDX_1ST_HALF )
            nExtraMatchesPerHalfSeason++;
          else
            anMatchdaysPerMoveday[i]++;
        }

        // One additional day for second season half.
        //
        nExtraMatchesPerHalfSeason = nNumExtraMatchesPerMoveday / 2 + 1;

        for ( int i = LAST_IDX_2ND_HALF; i > LAST_IDX_2ND_HALF - nExtraMatchesPerHalfSeason; i-- )
        {
          if ( fExcludeLastMoveday && i == LAST_IDX_2ND_HALF )
            nExtraMatchesPerHalfSeason++;
          else
            anMatchdaysPerMoveday[i]++;
        }
      }
    }

    // Now, let's do the real work and attach the matchdays to the correct movedays
    //
    int cntMovedays = 0;
    int cntMatchdays = 0;

    // Start with first half of the season
    //
    for ( int i = 0; i < anMatchdaysPerMoveday.length / 2; i++ )
    {
      GMoveday moveday = listMovedays.get( cntMovedays++ );

      for ( int j = 0; j < anMatchdaysPerMoveday[i]; j++ )
        moveday.addMatchday( listMatchdays.get( cntMatchdays++ ) );
    }

    // If we had an impair number of movedays, we have to increase the counter
    //
    if ( fDontUseMiddleMoveday )
      cntMovedays++;

    // Start with second half of the season
    //
    for ( int i = anMatchdaysPerMoveday.length / 2; i < anMatchdaysPerMoveday.length; i++ )
    {
      GMoveday moveday = listMovedays.get( cntMovedays++ );

      for ( int j = 0; j < anMatchdaysPerMoveday[i]; j++ )
        moveday.addMatchday( listMatchdays.get( cntMatchdays++ ) );
    }

    assert cntMatchdays == listMatchdays.size() : "Logical error -> not all matchdays have been attached to movedays. "
        + "Contest element: " + getId();
  }

  @Override
  public String getMoveMakingImplName( GComputerUser user )
  {
    // If the goal account for this contest element is reused by following rounds, we have
    // to use a special move-maker.
    //
    if ( getContestRound().getRemainingMatchesSameGA() > 0 )
      return user.getImplNameRoundRobinMultiRound();

    return user.getImplNameRoundRobin();
  }

  @Override
  public int getNumTeams()
  {
    return d_nNumTeams;
  }

  /**
   * Returns the number of times each team plays against all other teams.
   */
  public int getNumRounds()
  {
    return d_nNumRounds;
  }

  public int getNumPromoted1()
  {
    return d_nNumPromoted1;
  }

  public int getNumPromoted2()
  {
    return d_nNumPromoted2;
  }

  public int getNumRelegated()
  {
    return d_nNumRelegated;
  }

  public Class<? extends IGEntity> getGEntityClass()
  {
    return GRoundRobinContestElement.class;
  }
}
